using System;
using System.Threading.Tasks;

namespace GameOfLifeX.Core
{
	public class Model
	{
		private bool[,] _grid;
		private readonly int _gridWidth;
		private readonly int _gridHeight;

		public Model (int gridWidth, int gridHeight)
		{
			_gridWidth = gridWidth;
			_gridHeight = gridHeight;
			_grid = new bool[gridWidth, gridHeight];
		}

		public void SeedRandomly ()
		{
			var r = new Random (DateTime.Now.Millisecond);
			for (int i = 0; i < _gridWidth; i++)
				for (int j = 0; j < _gridHeight; j++)
					_grid [i, j] = r.Next(15) == 1;                
		}
		//1.Any live cell with fewer than two live neighbours dies, as if caused by under-population.
		//2.Any live cell with two or three live neighbours lives on to the next generation.
		//3.Any live cell with more than three live neighbours dies, as if by overcrowding.
		//4.Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
		public void RunGeneration ()
		{
			var tempGrid = new bool[_gridWidth, _gridHeight];                                    		

			for (int x = 0; x < _gridWidth; x++)
				for (int y = 0; y < _gridHeight; y++)
				{
					int neighbourCount = CountNeighbours (x, y);
					bool isThisLiveCell = _grid [x, y];
					bool cellSurvivesOrIsBorn = 
						(isThisLiveCell && (neighbourCount == 2 || neighbourCount == 3)) ||
						(!isThisLiveCell) && (neighbourCount == 3);
					tempGrid [x, y] = cellSurvivesOrIsBorn;                    
				}		
			_grid = tempGrid;
		}

		public void RunGenerationParallel()
		{
			var tempGrid = new bool[_gridWidth, _gridHeight];       

			Parallel.For (0,_gridWidth, (x)=>
				{
					for (int y = 0; y < _gridHeight; y++)
					{
						int neighbourCount = CountNeighbours (x, y);
						bool isThisLiveCell = _grid [x, y];
						bool cellSurvivesOrIsBorn = 
							(isThisLiveCell && (neighbourCount == 2 || neighbourCount == 3)) ||
							(!isThisLiveCell) && (neighbourCount == 3);
						tempGrid [x, y] = cellSurvivesOrIsBorn;                    
					}	
				});
		}

		public void GenerateCellsAtTouchLocation (float width, float height, float x, float y)
		{
			// 1. find x/y for cellen der svarer til dette touch

			var xFactor = _gridWidth / width;
			var yFactor = _gridHeight / height;

			int convertedX =(int) Math.Round ( x * xFactor);
			int convertedY =(int) Math.Round ( y * yFactor);


			SetGridValue (convertedX-1, convertedY, true);
			SetGridValue (convertedX, convertedY, true);
			SetGridValue (convertedX+1, convertedY, true);

			SetGridValue (convertedX-1, convertedY-1, true);
			SetGridValue (convertedX, convertedY-1, true);
			SetGridValue (convertedX+1, convertedY-1, true);

			SetGridValue (convertedX-1, convertedY+1, true);
			SetGridValue (convertedX, convertedY+1, true);
			SetGridValue (convertedX+1, convertedY+1, true);
			//throw new NotImplementedException ();
		}

		private int CountNeighbours (int px, int py)
		{
			int count = 0;
			count += GetGridValue (px - 1, py - 1) ? 1 : 0;  // north west
			count += GetGridValue (px, py - 1) ? 1 : 0;      // north
			count += GetGridValue (px + 1, py - 1) ? 1 : 0;  // north east
			count += GetGridValue (px - 1, py) ? 1 : 0;      // west  
			count += GetGridValue (px + 1, py) ? 1 : 0;      // east            
			count += GetGridValue (px - 1, py + 1) ? 1 : 0;  // south west            
			count += GetGridValue (px, py + 1) ? 1 : 0;      // south                                    
			count += GetGridValue (px + 1, py + 1) ? 1 : 0;  // south east            
			return count;
		}

		private bool GetGridValue (int x, int y)
		{
			if (x >= _gridWidth)
				x = x - _gridWidth;

			if (x < 0)
				x = x + _gridWidth;

			if (y >= _gridHeight)
				y = y - _gridHeight;

			if (y < 0)
				y = y + _gridHeight;

			return _grid [x, y];           
		}

		private void SetGridValue (int x, int y, bool value)
		{
			if (x >= _gridWidth)
				x = x - _gridWidth;

			if (x < 0)
				x = x + _gridWidth;

			if (y >= _gridHeight)
				y = y - _gridHeight;

			if (y < 0)
				y = y + _gridHeight;

			_grid [x, y] = value;
		}

		public bool[,] GetGrid ()
		{
			return _grid;
		}

		public int Height { get { return _gridHeight; } }

		public int Width { get { return _gridWidth; } }
	}
}
